#include "vtkTransformRepresentation3D.h"

#include <vtkProperty.h>
#include <vtkMath.h>
#include <vtkActor.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkMatrix4x4.h>
#include <vtkCellPicker.h>
#include <vtkCursor3D.h>
#include <vtkVector.h>
#include <vtkCamera.h>
#include <vtkFocalPlanePointPlacer.h>
#include <vtkArrowSource.h>
#include <vtkRegularPolygonSource.h>
#include <vtkPickingManager.h>
#include <vtkCoordinate.h>
#include <vtkEventData.h>
#include <vtkPropCollection.h>
#include <vtkInteractorObserver.h>
#include <vtkAssembly.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>

VTK_ABI_NAMESPACE_BEGIN

vtkStandardNewMacro(vtkTransformRepresentation3D);

vtkCxxSetObjectMacro(vtkTransformRepresentation3D, Property, vtkProperty);
vtkCxxSetObjectMacro(vtkTransformRepresentation3D, SelectedProperty, vtkProperty);

constexpr double epsilon = 1e-9;

/**
 * Checks if any component of the given point is close to zero but not exactly zero.
 *
 * @sa ProjectPointOntoCameraOrientationAxis()
 */
bool HasNearZeroValues(const double point[3])
{
    for (unsigned int idx = 0; idx < 3; ++idx)
    {
        if (point[idx] != 0.0 && std::abs(point[idx]) < epsilon)
        {
            return true;
        }
    }
    return false;
}

/**
 * This function projects a point onto a plane that is defined by the camera's
 * orientation axis and a reference point within that plane.
 * This is used to rectify a matrix calculation uncertainty given by ComputeDisplayToWorld
 * where the computed result is very close to zero (approximately on the order of 1e-12)
 * but not exactly zero, as it should be.
 */
void ProjectPointOntoCameraOrientationAxis(
    double projectedPoint[3], const double referencePoint[3], vtkCamera* camera)
{
    vtkVector3d referenceVector(referencePoint);
    vtkVector3d projectedVector(projectedPoint);
    vtkMatrix4x4* viewMatrix = camera->GetViewTransformMatrix();
    vtkVector3d cameraAxis(
        viewMatrix->GetElement(2, 0), viewMatrix->GetElement(2, 1), viewMatrix->GetElement(2, 2));
    double projection = (projectedVector - referenceVector).Dot(cameraAxis);
    vtkVector3d adjustedResult = projectedVector - (cameraAxis * projection);
    projectedPoint[0] = adjustedResult[0];
    projectedPoint[1] = adjustedResult[1];
    projectedPoint[2] = adjustedResult[2];
}

//==========================================vtkTransformRepresentation3D================================
Arrow_t::Arrow_t() :
    Source(vtkArrowSource::New()),
    Mapper(vtkPolyDataMapper::New()),
    Actor(vtkActor::New())
{
    Source->SetShaftRadius(0.01);
    Source->SetTipRadius(0.1);

    Mapper->SetInputConnection(Source->GetOutputPort());
    Actor->SetMapper(Mapper);
}

Arrow_t::~Arrow_t()
{
    this->Source->Delete();
    this->Mapper->Delete();
    this->Actor->Delete();
}

Circle_t::Circle_t() :
    Source(vtkRegularPolygonSource::New()),
    Mapper(vtkPolyDataMapper::New()),
    Actor(vtkActor::New())
{
    Source->GeneratePolygonOff();
    Source->SetNumberOfSides(50);
    Source->SetRadius(3);
    Source->SetCenter(0, 0, 0);

    Mapper->SetInputConnection(Source->GetOutputPort());
    Actor->SetMapper(Mapper);

    Actor->GetProperty()->EdgeVisibilityOn();
    Actor->GetProperty()->SetLineWidth(2);
    Actor->GetProperty()->SetRepresentationToWireframe();
}

Circle_t::~Circle_t()
{
    this->Source->Delete();
    this->Mapper->Delete();
    this->Actor->Delete();
}

vtkTransformRepresentation3D::vtkTransformRepresentation3D():
    Arrows(),
    Circles()
{
    // Initialize state
    this->InteractionState = vtkHandleRepresentation::Outside;

    // Represent the line
    this->Cursor3D = vtkCursor3D::New();
    this->Cursor3D->AllOff();
    this->Cursor3D->AxesOn();
    this->Cursor3D->TranslationModeOn();

    this->Mapper = vtkPolyDataMapper::New();
    this->Mapper->SetInputConnection(this->Cursor3D->GetOutputPort());

    // Set up the initial properties
    this->CreateDefaultProperties();

    this->Actor = vtkActor::New();
    this->Actor->SetMapper(this->Mapper);
    this->Actor->SetProperty(this->Property);

    auto& [xArrow,yArrow,zArrow] {Arrows};

    xArrow.Actor->GetProperty()->SetColor(1.0, 0.0, 0.0);
    xArrow.Actor->SetPosition(0.5, 0.0, 0.0);
    xArrow.Actor->SetOrientation(90, 0, 0);

    yArrow.Actor->GetProperty()->SetColor(0.0, 1.0, 0.0);
    yArrow.Actor->SetPosition(0.0, 0.5, 0.0);
    yArrow.Actor->SetOrientation(0, 0, 90);

    zArrow.Actor->GetProperty()->SetColor(0.0, 0.0, 1.0);
    zArrow.Actor->SetPosition(0.0, 0.0, -0.5);
    zArrow.Actor->SetOrientation(0, 90, 0);

    auto& [xCircle,yCircle,zCircle] {Circles};

    xCircle.Actor->GetProperty()->SetColor(1.0, 0.0, 0.0);
    xCircle.Actor->SetOrientation(90, 0, 0);

    yCircle.Actor->GetProperty()->SetColor(0.0, 1.0, 0.0);
    yCircle.Actor->SetOrientation(0, 0, 90);

    yCircle.Actor->GetProperty()->SetColor(0.0, 0.0, 1.0);
    yCircle.Actor->SetOrientation(0, 90, 0);

    //添加所有角色到组
    Assembly = vtkAssembly::New();
    Assembly->AddPart(xArrow.Actor);
    Assembly->AddPart(yArrow.Actor);
    Assembly->AddPart(zArrow.Actor);
    Assembly->AddPart(xCircle.Actor);
    Assembly->AddPart(yCircle.Actor);
    Assembly->AddPart(zCircle.Actor);
    Assembly->AddPart(Actor);

    // Manage the picking stuff
    this->CursorPicker = vtkCellPicker::New();
    this->CursorPicker->PickFromListOn();
    this->CursorPicker->AddPickList(this->Actor);
    this->CursorPicker->AddPickList(xArrow.Actor);
    this->CursorPicker->AddPickList(yArrow.Actor);
    this->CursorPicker->AddPickList(zArrow.Actor);
    this->CursorPicker->AddPickList(xCircle.Actor);
    this->CursorPicker->AddPickList(yCircle.Actor);
    this->CursorPicker->AddPickList(zCircle.Actor);
    this->CursorPicker->SetTolerance(0.01); // need some fluff

    // Override superclass'
    this->PlaceFactor = 1.0;

    // The size of the hot spot
    this->HotSpotSize = 0.05;
    this->WaitingForMotion = 0;
    this->ConstraintAxis = -1;

    // Current handle size
    this->HandleSize = 15.0; // in pixels
    this->CurrentHandleSize = this->HandleSize;

    // Translation control
    this->TranslationMode = 1;

    vtkFocalPlanePointPlacer* pointPlacer = vtkFocalPlanePointPlacer::New();
    this->SetPointPlacer(pointPlacer);
    pointPlacer->Delete();

    // Continuous moves
    this->SmoothMotion = 1;
}

vtkTransformRepresentation3D::~vtkTransformRepresentation3D()
{
    this->Cursor3D->Delete();
    this->CursorPicker->Delete();
    this->Mapper->Delete();
    this->Actor->Delete();
    this->Property->Delete();
    this->SelectedProperty->Delete();
    this->Assembly->Delete();
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::RegisterPickers()
{
    vtkPickingManager* pm = this->GetPickingManager();
    if (!pm)
    {
        return;
    }
    pm->AddPicker(this->CursorPicker, this);
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::PlaceWidget(double bds[6])
{
    int i;
    double bounds[6], center[3];

    this->AdjustBounds(bds, bounds, center);

    this->Cursor3D->SetModelBounds(bounds);
    this->SetWorldPosition(center);

    for (i = 0; i < 6; i++)
    {
        this->InitialBounds[i] = bounds[i];
    }
    this->InitialLength = sqrt((bounds[1] - bounds[0]) * (bounds[1] - bounds[0]) +
                               (bounds[3] - bounds[2]) * (bounds[3] - bounds[2]) +
                               (bounds[5] - bounds[4]) * (bounds[5] - bounds[4]));
}

//------------------------------------------------------------------------------
double* vtkTransformRepresentation3D::GetBounds()
{
    return this->Cursor3D->GetModelBounds();
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::SetWorldPosition(double p[3])
{
    if (this->Renderer && this->PointPlacer)
    {
        if (this->PointPlacer->ValidateWorldPosition(p))
        {
            this->Cursor3D->SetFocalPoint(p); // this may clamp the point
            this->WorldPosition->SetValue(this->Cursor3D->GetFocalPoint());
            this->WorldPositionTime.Modified();
        }
    }
    else
    {
        this->Cursor3D->SetFocalPoint(p); // this may clamp the point
        this->WorldPosition->SetValue(this->Cursor3D->GetFocalPoint());
        this->WorldPositionTime.Modified();
    }
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::SetDisplayPosition(double p[3])
{
    if (this->Renderer && this->PointPlacer)
    {
        if (this->PointPlacer->ValidateDisplayPosition(this->Renderer, p))
        {
            double worldPos[3], worldOrient[9];
            if (this->PointPlacer->ComputeWorldPosition(this->Renderer, p, worldPos, worldOrient))
            {
                this->DisplayPosition->SetValue(p);
                this->WorldPosition->SetValue(worldPos);
                this->DisplayPositionTime.Modified();
                this->SetWorldPosition(this->WorldPosition->GetValue());
            }
        }
    }
    else
    {
        this->DisplayPosition->SetValue(p);
        this->DisplayPositionTime.Modified();
    }
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::SetHandleSize(double size)
{
    this->Superclass::SetHandleSize(size);
    this->CurrentHandleSize = this->HandleSize;
}

bool vtkTransformRepresentation3D::NearbyEvent(int X, int Y, double bounds[6])
{
    double worldPt[4];

    vtkInteractorObserver::ComputeDisplayToWorld(
        this->Renderer, X, Y, 0, worldPt);

    // Compare, in screen space, the position of the cursor relative to the center of the bounds
    if ((worldPt[0] >= bounds[0] && worldPt[0] <= bounds[1] &&
         worldPt[1] >= bounds[2] && worldPt[1] <= bounds[3] /*&&
         worldPt[2] >= bounds[4] && worldPt[2] <= bounds[5]*/))
    {
        return true;
    }

    return false;
}


//------------------------------------------------------------------------------
int vtkTransformRepresentation3D ::ComputeInteractionState(int X, int Y, int vtkNotUsed(modify))
{
    this->VisibilityOn(); // actor must be on to be picked

    // First make sure that the cursor is within the bounding sphere of the
    // representation in display space.
    double d[3], bounds[6];
    this->Assembly->GetBounds(bounds);
    this->GetDisplayPosition(d);

    if (!this->NearbyEvent(X, Y, bounds))
    {
        this->InteractionState = vtkHandleRepresentation::Outside;
        return this->InteractionState;
    }

    // Now see if anything is picked
    vtkAssemblyPath* path = this->GetAssemblyPath(X, Y, 0., this->CursorPicker);

    if (path != nullptr)
    {
        this->InteractionState = vtkHandleRepresentation::Nearby;
    }
    else
    {
        this->InteractionState = vtkHandleRepresentation::Outside;
        if (this->ActiveRepresentation)
        {
            this->VisibilityOff();
        }
    }

    return this->InteractionState;
}

//------------------------------------------------------------------------------
int vtkTransformRepresentation3D::ComputeComplexInteractionState(
    vtkRenderWindowInteractor*, vtkAbstractWidget*, unsigned long, void* calldata, int)
{
    this->VisibilityOn(); // actor must be on to be picked

    vtkEventData* edata = static_cast<vtkEventData*>(calldata);
    vtkEventDataDevice3D* edd = edata->GetAsEventDataDevice3D();
    if (edd)
    {
        double pos[3];
        edd->GetWorldPosition(pos);
        vtkAssemblyPath* path = this->GetAssemblyPath3DPoint(pos, this->CursorPicker);
        double focus[3];
        this->Cursor3D->GetFocalPoint(focus);
        double d[3];
        this->GetDisplayPosition(d);

        if (path != nullptr)
        {
            this->InteractionState = vtkHandleRepresentation::Nearby;
        }
        else
        {
            this->InteractionState = vtkHandleRepresentation::Outside;
            if (this->ActiveRepresentation)
            {
                this->VisibilityOff();
            }
        }
    }

    return this->InteractionState;
}

//------------------------------------------------------------------------------
int vtkTransformRepresentation3D::DetermineConstraintAxis(
    int constraint, double* x, double* startPickPoint)
{
    // Look for trivial cases
    if (!this->Constrained)
    {
        return -1;
    }
    else if (constraint >= 0 && constraint < 3)
    {
        return constraint;
    }

    // Okay, figure out constraint. First see if the choice is
    // outside the hot spot
    if (!x)
    {
        double p[3], d2, tol;
        this->CursorPicker->GetPickPosition(p);
        d2 = vtkMath::Distance2BetweenPoints(p, this->LastPickPosition);
        tol = this->HotSpotSize * this->InitialLength;
        if (d2 > (tol * tol))
        {
            this->WaitingForMotion = 0;
            return this->CursorPicker->GetCellId();
        }
        else
        {
            this->WaitingForMotion = 1;
            this->WaitCount = 0;
            return -1;
        }
    }
    else if (x)
    {
        this->WaitingForMotion = 0;
        double v[3];
        v[0] = fabs(x[0] - startPickPoint[0]);
        v[1] = fabs(x[1] - startPickPoint[1]);
        v[2] = fabs(x[2] - startPickPoint[2]);
        return (v[0] > v[1] ? (v[0] > v[2] ? 0 : 2) : (v[1] > v[2] ? 1 : 2));
    }
    else
    {
        return -1;
    }
}

//------------------------------------------------------------------------------
// Record the current event position, and the translation state
void vtkTransformRepresentation3D::StartWidgetInteraction(double startEventPos[2])
{
    this->StartEventPosition[0] = startEventPos[0];
    this->StartEventPosition[1] = startEventPos[1];
    this->StartEventPosition[2] = 0.0;

    this->LastEventPosition[0] = startEventPos[0];
    this->LastEventPosition[1] = startEventPos[1];

    // Make sure events are close to widget and something is picked
    double bounds[6];
    this->Cursor3D->GetModelBounds(bounds);
    bool nearby = this->NearbyEvent(startEventPos[0], startEventPos[1], bounds);
    vtkAssemblyPath* path =
        this->GetAssemblyPath(startEventPos[0], startEventPos[1], 0., this->CursorPicker);

    if (nearby && path != nullptr)
    {
        this->InteractionState = vtkHandleRepresentation::Nearby;
        this->ConstraintAxis = -1;
        this->CursorPicker->GetPickPosition(this->LastPickPosition);
    }
    else
    {
        this->InteractionState = vtkHandleRepresentation::Outside;
        this->ConstraintAxis = -1;
    }
    this->Cursor3D->SetTranslationMode(this->TranslationMode);
    this->WaitCount = 0;
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::StartComplexInteraction(
    vtkRenderWindowInteractor*, vtkAbstractWidget*, unsigned long, void* calldata)
{
    vtkEventData* edata = static_cast<vtkEventData*>(calldata);
    vtkEventDataDevice3D* edd = edata->GetAsEventDataDevice3D();
    if (edd)
    {
        edd->GetWorldPosition(this->StartEventPosition);
        this->LastEventPosition[0] = this->StartEventPosition[0];
        this->LastEventPosition[1] = this->StartEventPosition[1];
        this->LastEventPosition[2] = this->StartEventPosition[2];

        double bounds[6];
        this->Cursor3D->GetModelBounds(bounds);
        bool nearby =
            this->NearbyEvent(this->StartEventPosition[0], this->StartEventPosition[1], bounds);
        vtkAssemblyPath* path =
            this->GetAssemblyPath3DPoint(this->StartEventPosition, this->CursorPicker);

        if (nearby && path != nullptr)
        {
            this->InteractionState = vtkHandleRepresentation::Nearby;
            this->ConstraintAxis = -1;
            this->CursorPicker->GetPickPosition(this->LastPickPosition);
        }
        else
        {
            this->InteractionState = vtkHandleRepresentation::Outside;
            this->ConstraintAxis = -1;
        }
        this->Cursor3D->SetTranslationMode(this->TranslationMode);
        this->WaitCount = 0;
    }
}

//------------------------------------------------------------------------------
// Based on the displacement vector (computed in display coordinates) and
// the cursor state (which corresponds to which part of the widget has been
// selected), the widget points are modified.
// First construct a local coordinate system based on the display coordinates
// of the widget.
void vtkTransformRepresentation3D::WidgetInteraction(double eventPos[2])
{
    // Do different things depending on state
    // Calculations everybody does
    double focalPoint[4], pickPoint[4], prevPickPoint[4], startPickPoint[4], z;

    // Compute the two points defining the motion vector
    vtkInteractorObserver::ComputeWorldToDisplay(this->Renderer, this->LastPickPosition[0],
                                                 this->LastPickPosition[1], this->LastPickPosition[2], focalPoint);
    z = focalPoint[2];
    vtkInteractorObserver::ComputeDisplayToWorld(
        this->Renderer, this->LastEventPosition[0], this->LastEventPosition[1], z, prevPickPoint);
    vtkInteractorObserver::ComputeDisplayToWorld(
        this->Renderer, eventPos[0], eventPos[1], z, pickPoint);

    // Process the motion
    if (this->InteractionState == vtkHandleRepresentation::Selecting ||
        this->InteractionState == vtkHandleRepresentation::Translating)
    {
        this->WaitCount++;

        if (this->WaitCount > 3 || !this->Constrained)
        {
            vtkInteractorObserver::ComputeDisplayToWorld(this->Renderer, this->StartEventPosition[0],
                                                         this->StartEventPosition[1], z, startPickPoint);

            this->ConstraintAxis =
                this->DetermineConstraintAxis(this->ConstraintAxis, pickPoint, startPickPoint);

            if (this->InteractionState == vtkHandleRepresentation::Selecting && !this->TranslationMode)
            {
                vtkDebugMacro(<< "Processing widget interaction for Select mode");

                // If we are doing axis constrained motion, ignore the placer.
                // Can't have both the placer and an axis constraint dictating
                // handle placement.
                if (this->ConstraintAxis >= 0 || this->Constrained || !this->PointPlacer)
                {
                    this->MoveFocus(prevPickPoint, pickPoint);
                }
                else
                {
                    double newCenterPointRequested[3]; // displayPosition
                    double newCenterPoint[3], worldOrient[9];

                    // Make a request for the new position.
                    this->MoveFocusRequest(prevPickPoint, pickPoint, eventPos, newCenterPointRequested);

                    vtkFocalPlanePointPlacer* fPlacer =
                        vtkFocalPlanePointPlacer::SafeDownCast(this->PointPlacer);
                    if (fPlacer)
                    {
                        // Offset the placer plane to one that passes through the current
                        // world position and is parallel to the focal plane. Offset =
                        // the distance currentWorldPos is from the focal plane
                        //
                        double currentWorldPos[3], projDir[3], fp[3];
                        this->GetWorldPosition(currentWorldPos);
                        this->Renderer->GetActiveCamera()->GetFocalPoint(fp);
                        double vec[3] = { currentWorldPos[0] - fp[0], currentWorldPos[1] - fp[1],
                                         currentWorldPos[2] - fp[2] };
                        this->Renderer->GetActiveCamera()->GetDirectionOfProjection(projDir);
                        fPlacer->SetOffset(vtkMath::Dot(vec, projDir));
                    }

                    vtkDebugMacro(<< "Request for computing world position at "
                                  << "display position of " << newCenterPointRequested[0] << ","
                                  << newCenterPointRequested[1]);

                    // See what the placer says.
                    if (this->PointPlacer->ComputeWorldPosition(
                            this->Renderer, newCenterPointRequested, newCenterPoint, worldOrient))
                    {
                        // Once the placer has validated us, update the handle position
                        this->SetWorldPosition(newCenterPoint);
                    }
                }
            }
            else
            {
                vtkDebugMacro(<< "Processing widget interaction for translate");

                // If we are doing axis constrained motion, ignore the placer.
                // Can't have both the placer and the axis constraint dictating
                // handle placement.
                if (this->ConstraintAxis >= 0 || this->Constrained || !this->PointPlacer)
                {
                    this->Translate(prevPickPoint, pickPoint);
                }
                else
                {
                    double newCenterPointRequested[3]; // displayPosition
                    double newCenterPoint[3], worldOrient[9];

                    // Make a request for the new position.
                    this->MoveFocusRequest(prevPickPoint, pickPoint, eventPos, newCenterPointRequested);

                    vtkFocalPlanePointPlacer* fPlacer =
                        vtkFocalPlanePointPlacer::SafeDownCast(this->PointPlacer);
                    if (fPlacer)
                    {
                        // Offset the placer plane to one that passes through the current
                        // world position and is parallel to the focal plane. Offset =
                        // the distance currentWorldPos is from the focal plane
                        //
                        double currentWorldPos[3], projDir[3], fp[3];
                        this->GetWorldPosition(currentWorldPos);
                        this->Renderer->GetActiveCamera()->GetFocalPoint(fp);
                        double vec[3] = { currentWorldPos[0] - fp[0], currentWorldPos[1] - fp[1],
                                         currentWorldPos[2] - fp[2] };
                        this->Renderer->GetActiveCamera()->GetDirectionOfProjection(projDir);
                        fPlacer->SetOffset(vtkMath::Dot(vec, projDir));
                    }

                    vtkDebugMacro(<< "Request for computing world position at "
                                  << "display position of " << newCenterPointRequested[0] << ","
                                  << newCenterPointRequested[1]);

                    // See what the placer says.
                    if (this->PointPlacer->ComputeWorldPosition(
                            this->Renderer, newCenterPointRequested, newCenterPoint, worldOrient))
                    {
                        if (::HasNearZeroValues(newCenterPoint))
                        {
                            ::ProjectPointOntoCameraOrientationAxis(
                                newCenterPoint, this->LastPickPosition, this->Renderer->GetActiveCamera());
                        }
                        this->SetWorldPosition(newCenterPoint);
                    }
                }
            }
        }
    }

    else if (this->InteractionState == vtkHandleRepresentation::Scaling)
    {
        // Scaling does not change the position of the handle, we needn't
        // ask the placer..
        this->Scale(prevPickPoint, pickPoint, eventPos);
    }

    // Book keeping
    this->LastEventPosition[0] = eventPos[0];
    this->LastEventPosition[1] = eventPos[1];

    this->Modified();
}

void vtkTransformRepresentation3D::ComplexInteraction(
    vtkRenderWindowInteractor*, vtkAbstractWidget*, unsigned long, void* calldata)
{
    vtkEventData* edata = static_cast<vtkEventData*>(calldata);
    vtkEventDataDevice3D* edd = edata->GetAsEventDataDevice3D();
    if (edd)
    {
        double eventPos[3];
        edd->GetWorldPosition(eventPos);
        // Process the motion
        if (this->InteractionState == vtkHandleRepresentation::Selecting ||
            this->InteractionState == vtkHandleRepresentation::Translating)
        {
            this->WaitCount++;

            if (this->WaitCount > 3 || !this->Constrained)
            {
                this->ConstraintAxis =
                    this->DetermineConstraintAxis(this->ConstraintAxis, eventPos, this->StartEventPosition);

                if (this->InteractionState == vtkHandleRepresentation::Selecting && !this->TranslationMode)
                {
                    vtkDebugMacro(<< "Processing widget interaction for Select mode");

                    this->MoveFocus(this->LastEventPosition, eventPos);
                }
                else
                {
                    vtkDebugMacro(<< "Processing widget interaction for translate");
                    this->Translate(this->LastEventPosition, eventPos);
                }
            }
        }

        // Book keeping
        this->LastEventPosition[0] = eventPos[0];
        this->LastEventPosition[1] = eventPos[1];
        this->LastEventPosition[2] = eventPos[2];

        this->Modified();
    }
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D ::MoveFocusRequest(
    const double* p1, const double* p2, const double eventPos[2], double center[3])
{
    if (this->SmoothMotion)
    {
        double focus[4], v[3];
        this->Cursor3D->GetFocalPoint(focus);
        this->GetTranslationVector(p1, p2, v);

        // Move the center of the handle along the motion vector
        focus[0] += v[0];
        focus[1] += v[1];
        focus[2] += v[2];
        focus[3] = 1.0;

        // Get the display position that this center would fall on.
        this->Renderer->SetWorldPoint(focus);
        this->Renderer->WorldToDisplay();
        this->Renderer->GetDisplayPoint(center);
    }
    else
    {
        center[0] = eventPos[0];
        center[1] = eventPos[1];
        center[2] = 1.0;
    }
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::MoveFocus(const double* p1, const double* p2)
{
    this->Translate(p1, p2);
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::SetTranslationMode(vtkTypeBool mode)
{
    if (this->TranslationMode != mode)
    {
        this->TranslationMode = mode;
        // Pass new setting to Cursor3D, otherwise PlaceWidget will not work
        // as it should when TranslationMode is off.
        this->Cursor3D->SetTranslationMode(mode);
        this->Modified();
    }
}

//------------------------------------------------------------------------------
// Translate everything
void vtkTransformRepresentation3D::Translate(const double* p1, const double* p2)
{
    double v[3] = { 0, 0, 0 };
    this->GetTranslationVector(p1, p2, v);
    this->vtkHandleRepresentation::Translate(v);

    double* bounds = this->Cursor3D->GetModelBounds();
    double* pos = this->Cursor3D->GetFocalPoint();
    double newBounds[6], newFocus[3];

    if (this->ConstraintAxis >= 0)
    {
        for (int i = 0; i < 3; i++)
        {
            if (i != this->ConstraintAxis)
            {
                v[i] = 0.0;
            }
        }
    }

    for (int i = 0; i < 3; i++)
    {
        newBounds[2 * i] = bounds[2 * i] + v[i];
        newBounds[2 * i + 1] = bounds[2 * i + 1] + v[i];
        newFocus[i] = pos[i] + v[i];
    }

    this->Cursor3D->SetModelBounds(newBounds);
    this->Cursor3D->SetFocalPoint(newFocus);
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::SizeBounds()
{
    // Only change the size of the bounding box if translation mode is on.
    if (this->TranslationMode)
    {
        double center[3], bounds[6];
        this->Cursor3D->GetFocalPoint(center);
        double radius = this->SizeHandlesInPixels(1.0, center);
        radius *= this->CurrentHandleSize / this->HandleSize;

        for (int i = 0; i < 3; i++)
        {
            bounds[2 * i] = center[i] - radius;
            bounds[2 * i + 1] = center[i] + radius;
        }
        this->Cursor3D->SetModelBounds(bounds);
    }
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::Scale(
    const double* p1, const double* p2, const double eventPos[2])
{
    // Get the motion vector
    double v[3];
    v[0] = p2[0] - p1[0];
    v[1] = p2[1] - p1[1];
    v[2] = p2[2] - p1[2];

    double* bounds = this->Cursor3D->GetModelBounds();

    // Compute the scale factor
    double sf = vtkMath::Norm(v) /
                sqrt((bounds[1] - bounds[0]) * (bounds[1] - bounds[0]) +
                     (bounds[3] - bounds[2]) * (bounds[3] - bounds[2]) +
                     (bounds[5] - bounds[4]) * (bounds[5] - bounds[4]));

    if (eventPos[1] > this->LastEventPosition[1])
    {
        sf = 1.0 + sf;
    }
    else
    {
        sf = 1.0 - sf;
    }

    this->CurrentHandleSize *= sf;
    this->CurrentHandleSize = (this->CurrentHandleSize < 0.001 ? 0.001 : this->CurrentHandleSize);

    this->SizeBounds();
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::Highlight(int highlight)
{
    if (highlight)
    {
        this->Actor->SetProperty(this->SelectedProperty);
    }
    else
    {
        this->Actor->SetProperty(this->Property);
    }
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::CreateDefaultProperties()
{
    this->Property = vtkProperty::New();
    this->Property->SetAmbient(1.0);
    this->Property->SetColor(1.0, 1.0, 1.0);
    this->Property->SetLineWidth(0.5);

    this->SelectedProperty = vtkProperty::New();
    this->SelectedProperty->SetAmbient(1.0);
    this->SelectedProperty->SetColor(0.0, 1.0, 0.0);
    this->SelectedProperty->SetLineWidth(2.0);
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::SetInteractionColor(double r, double g, double b)
{
    this->SelectedProperty->SetColor(r, g, b);
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::SetForegroundColor(double r, double g, double b)
{
    this->Property->SetColor(r, g, b);
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::SetVisibility(vtkTypeBool visible)
{
    this->Actor->SetVisibility(visible);
    // Forward to superclass
    this->Superclass::SetVisibility(visible);
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::BuildRepresentation()
{
    // The net effect is to resize the handle
    if (this->GetMTime() > this->BuildTime ||
        (this->Renderer && this->Renderer->GetVTKWindow() &&
         this->Renderer->GetVTKWindow()->GetMTime() > this->BuildTime))
    {
        if (!this->Placed)
        {
            this->ValidPick = 1;
            this->Placed = 1;
        }

        this->SizeBounds();
        this->Cursor3D->Update();
        this->BuildTime.Modified();
    }
}

//------------------------------------------------------------------------------
void
vtkTransformRepresentation3D::ShallowCopy(vtkProp* prop)
{
    vtkTransformRepresentation3D* rep = vtkTransformRepresentation3D::SafeDownCast(prop);
    if (rep)
    {
        this->SetOutline(rep->GetOutline());
        this->SetXShadows(rep->GetXShadows());
        this->SetYShadows(rep->GetYShadows());
        this->SetZShadows(rep->GetZShadows());
        this->SetTranslationMode(rep->GetTranslationMode());
        this->SetProperty(rep->GetProperty());
        this->Actor->SetProperty(rep->GetProperty());
        this->SetSelectedProperty(rep->GetSelectedProperty());
        this->SetHotSpotSize(rep->GetHotSpotSize());
    }
    this->Superclass::ShallowCopy(prop);
}

//------------------------------------------------------------------------------
void
vtkTransformRepresentation3D::DeepCopy(vtkProp* prop)
{
    vtkTransformRepresentation3D* rep = vtkTransformRepresentation3D::SafeDownCast(prop);
    if (rep)
    {
        this->SetOutline(rep->GetOutline());
        this->SetXShadows(rep->GetXShadows());
        this->SetYShadows(rep->GetYShadows());
        this->SetZShadows(rep->GetZShadows());
        this->SetTranslationMode(rep->GetTranslationMode());
        this->SetProperty(rep->GetProperty());
        this->Actor->SetProperty(rep->GetProperty());
        this->SetSelectedProperty(rep->GetSelectedProperty());
        this->SetHotSpotSize(rep->GetHotSpotSize());
    }
    this->Superclass::DeepCopy(prop);
}

//------------------------------------------------------------------------------
void
vtkTransformRepresentation3D::GetActors(vtkPropCollection* pc)
{
    if (!pc)
    {
        return;
    }
    pc->AddItem(this->Actor);


    for(auto& it : Arrows)
        {
        pc->AddItem(it.Actor);
    };

    for(auto& it : Circles)
    {
        pc->AddItem(it.Actor);
    };

    this->Superclass::GetActors(pc);
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::ReleaseGraphicsResources(vtkWindow* win)
{
    this->Actor->ReleaseGraphicsResources(win);
}

//------------------------------------------------------------------------------
int vtkTransformRepresentation3D::RenderOpaqueGeometry(vtkViewport* viewport)
{
    this->BuildRepresentation();

    // Sanity check
    double worldPos[3];
    this->GetWorldPosition(worldPos);
    if (worldPos[0] == VTK_DOUBLE_MAX)
    {
        return 0;
    }

    auto rec {
        this->Actor->RenderOpaqueGeometry(viewport) &&
        this->Arrows[0].Actor->RenderOpaqueGeometry(viewport) &&
        this->Arrows[1].Actor->RenderOpaqueGeometry(viewport) &&
        this->Arrows[2].Actor->RenderOpaqueGeometry(viewport) &&
        this->Circles[0].Actor->RenderOpaqueGeometry(viewport) &&
        this->Circles[1].Actor->RenderOpaqueGeometry(viewport) &&
        this->Circles[2].Actor->RenderOpaqueGeometry(viewport)
    };

    return rec;
}

//------------------------------------------------------------------------------
int vtkTransformRepresentation3D::RenderTranslucentPolygonalGeometry(vtkViewport* viewport)
{
    this->BuildRepresentation();

    // Sanity check
    double worldPos[3];
    this->GetWorldPosition(worldPos);
    if (worldPos[0] == VTK_DOUBLE_MAX)
    {
        return 0;
    }

    auto rec {
        this->Actor->RenderTranslucentPolygonalGeometry(viewport) &&
        this->Arrows[0].Actor->RenderTranslucentPolygonalGeometry(viewport) &&
        this->Arrows[1].Actor->RenderTranslucentPolygonalGeometry(viewport) &&
        this->Arrows[2].Actor->RenderTranslucentPolygonalGeometry(viewport) &&
        this->Circles[0].Actor->RenderTranslucentPolygonalGeometry(viewport) &&
        this->Circles[1].Actor->RenderTranslucentPolygonalGeometry(viewport) &&
        this->Circles[2].Actor->RenderTranslucentPolygonalGeometry(viewport)
    };

    return rec;
}
//------------------------------------------------------------------------------
vtkTypeBool vtkTransformRepresentation3D::HasTranslucentPolygonalGeometry()
{
    this->BuildRepresentation();

    auto rec {
             this->Actor->HasTranslucentPolygonalGeometry() ||
             this->Arrows[0].Actor->HasTranslucentPolygonalGeometry() ||
             this->Arrows[1].Actor->HasTranslucentPolygonalGeometry() ||
             this->Arrows[2].Actor->HasTranslucentPolygonalGeometry() ||
             this->Circles[0].Actor->HasTranslucentPolygonalGeometry() ||
             this->Circles[1].Actor->HasTranslucentPolygonalGeometry() ||
             this->Circles[2].Actor->HasTranslucentPolygonalGeometry()
    };

    return rec;
}

//------------------------------------------------------------------------------
void vtkTransformRepresentation3D::PrintSelf(ostream& os, vtkIndent indent)
{
    // Superclass typedef defined in vtkTypeMacro() found in vtkSetGet.h
    this->Superclass::PrintSelf(os, indent);

    os << indent << "Hot Spot Size: " << this->HotSpotSize << "\n";
    if (this->Property)
    {
        os << indent << "Property: " << this->Property << "\n";
    }
    else
    {
        os << indent << "Property: (none)\n";
    }
    if (this->SelectedProperty)
    {
        os << indent << "Selected Property: " << this->SelectedProperty << "\n";
    }
    else
    {
        os << indent << "Selected Property: (none)\n";
    }

    os << indent << "Outline: " << (this->GetOutline() ? "On\n" : "Off\n");
    os << indent << "XShadows: " << (this->GetXShadows() ? "On\n" : "Off\n");
    os << indent << "YShadows: " << (this->GetYShadows() ? "On\n" : "Off\n");
    os << indent << "ZShadows: " << (this->GetZShadows() ? "On\n" : "Off\n");

    os << indent << "Translation Mode: " << (this->TranslationMode ? "On\n" : "Off\n");
    os << indent << "SmoothMotion: " << this->SmoothMotion << endl;
}


VTK_ABI_NAMESPACE_END


